/*
 *                    COPYRIGHT NOTICE
 *
 *           This code is copyright protected by Freedom Inc.
 *
 *              2001-2012 All Rights Are Reserved
 */
package com.freedom.oldsong.util;

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
 * @ClassName ReflectUtils
 *
 * @author steven.wang
 *
 */
public final class ReflectUtils {

	
	/**
	 * Creates a new instance of ReflectUtils.
	 *
	 */
	private ReflectUtils(){
		
	}
	
	/**
	 * @name EMPTY_FIELD_ARRAY
	 * @type Field[]
	 */
	private static final Field[] EMPTY_FIELD_ARRAY = new Field[0];

	/**
	 * Gets a field value from an object by name.
	 * 
	 * @param object
	 *            the object to reflect, must not be null
	 * @param fieldName
	 *            the field name to obtain value, must not be null
	 * @param breakScope
	 *            whether to break scope restrictions using the
	 *            <code>setAccessible</code> method. <code>False</code> will
	 *            only match public fields.
	 * @return the field's value
	 * 
	 * @throws IllegalAccessException
	 *             if field's scope isn't public and break scope is false.
	 * @throws NoSuchFieldException
	 *             if the field is not exist.
	 */
	/**
	 * @name getFieldValue
	 *
	 * @param object
	 * @param fieldName
	 * @param breakScope
	 * @return
	 * @throws IllegalAccessException
	 * @throws NoSuchFieldException    
	 * Object     
	 * @throws 
	*/
	public static final Object getFieldValue(final Object object,
			final String fieldName, final boolean breakScope)
			throws IllegalAccessException, NoSuchFieldException {
		if (object == null) {
			throw new IllegalArgumentException("The object must not be null");
		}

		if (fieldName == null) {
			throw new IllegalArgumentException("The fieldName must not be null");
		}

		Field field = getField(object.getClass(), fieldName);
		return getFieldValue(field, object, breakScope);
	}

	/**
	 * Gets a field value from an object by field.
	 * 
	 * @param field
	 *            the field to obtain value, must not be null
	 * @param object
	 *            the object to call on, may be null for static fields
	 * @param breakScope
	 *            whether to break scope restrictions using the
	 *            <code>setAccessible</code> method. <code>False</code> will
	 *            only match public methods.
	 * @return the field value
	 * 
	 * @throws IllegalAccessException
	 *             if field's scope isn't public and break scope is false.
	 */
	/**
	 * @name getFieldValue
	 *
	 * @param field
	 * @param object
	 * @param breakScope
	 * @return
	 * @throws IllegalAccessException    
	 * Object     
	 * @throws 
	*/
	public static final Object getFieldValue(final Field field,
			final Object object, final boolean breakScope)
			throws IllegalAccessException {
		if (field == null) {
			throw new IllegalArgumentException("The field must not be null");
		}

		if (!(breakScope && Modifier.isPublic(field.getModifiers()))) {
			AccessController.doPrivileged(new PrivilegedAction<Void>() {
                public Void run() {
                	field.setAccessible(true);
                	return null;
                }
            });

		}

		Object value = field.get(object);

		return value;
	}

	/**
	 * Sets a Field value to an object by name.
	 * 
	 * @param object
	 *            the object to reflect, must not be null
	 * @param fieldName
	 *            the field name to obtain, must not be null
	 * @param value
	 *            the field's new value
	 * @param breakScope
	 *            whether to break scope restrictions using the
	 *            <code>setAccessible</code> method. <code>False</code> will
	 *            only match public fields.
	 * 
	 * @throws IllegalAccessException
	 *             if field's scope isn't public and break scope is false.
	 * @throws NoSuchFieldException
	 *             if the field is not exist.
	 */
	/**
	 * @name setFieldValue
	 *
	 * @param object
	 * @param fieldName
	 * @param value
	 * @param breakScope
	 * @throws IllegalAccessException
	 * @throws NoSuchFieldException    
	 * void     
	 * @throws 
	*/
	public static final void setFieldValue(final Object object,
			final String fieldName, final Object value, final boolean breakScope)
			throws IllegalAccessException, NoSuchFieldException {
		if (object == null) {
			throw new IllegalArgumentException("The object must not be null");
		}

		if (fieldName == null) {
			throw new IllegalArgumentException("The fieldName must not be null");
		}

		Field field = getField(object.getClass(), fieldName);
		setFieldValue(field, object, value, breakScope);
	}

	/**
	 * Gets a field value from object by field.
	 * 
	 * @param field
	 *            the field to use, must not be null
	 * @param object
	 *            the object to call on, may be null for static fields
	 * @param value
	 *            the field's new value
	 * @param breakScope
	 *            whether to break scope restrictions using the
	 *            <code>setAccessible</code> method. <code>False</code> will
	 *            only match public methods.
	 * 
	 * @throws IllegalAccessException
	 *             if field's scope isn't public and break scope is false.
	 */
	/**
	 * @name setFieldValue
	 *
	 * @param field
	 * @param object
	 * @param value
	 * @param breakScope
	 * @throws IllegalAccessException    
	 * void     
	 * @throws 
	*/
	public static final void setFieldValue(final Field field,
			final Object object, final Object value, final boolean breakScope)
			throws IllegalAccessException {
		if (field == null) {
			throw new IllegalArgumentException("The field must not be null");
		}

		if (object == null) {
			throw new IllegalArgumentException("The object must not be null");
		}

		if (!(breakScope && Modifier.isPublic(field.getModifiers()))) {
			AccessController.doPrivileged(new PrivilegedAction<Void>() {
                public Void run() {
                	field.setAccessible(true);
                	return null;
                }
            });
		}

		field.set(object, value);

	}

	/**
	 * Gets an accessible <code>Field</code> by name breaking scope if
	 * requested. Superclasses/interfaces will be considered.
	 * 
	 * @param clazz
	 *            the class to reflect, must not be null
	 * @param fieldName
	 *            the field name to obtain, must not be null
	 * @return the Field object
	 * 
	 * @throws NoSuchFieldException
	 *             if the field is not exist.
	 */
	/**
	 * @name getField
	 *
	 * @param clazz
	 * @param fieldName
	 * @return
	 * @throws NoSuchFieldException    
	 * Field     
	 * @throws 
	*/
	public static final Field getField(final Class<?> clazz,
			final String fieldName) throws NoSuchFieldException {
		if (clazz == null) {
			throw new IllegalArgumentException("The class must not be null");
		}

		if (fieldName == null) {
			throw new IllegalArgumentException("The fieldName must not be null");
		}

		// Sun Java 1.3 has a bugged implementation of getField hence we write
		// the
		// code ourselves

		// getField() will return the Field object with the declaring class
		// set correctly to the class that declares the field. Thus requesting
		// the
		// field on a subclass will return the field from the superclass.
		//
		// priority order for lookup:
		// searchclass private/protected/package/public
		// superclass protected/package/public
		// private/different package blocks access to further superclasses
		// implementedinterface public

		// check up the superclass hierarchy
		Class<?> acls = clazz;
		Field match = null;
		while (acls != null && acls != Object.class) {

			// getDeclaredField checks for non-public scopes as well
			// and it returns accurate results
			try {
				Field field = acls.getDeclaredField(fieldName);
				match = field;
				break;

			} catch (NoSuchFieldException ignore) {
				assert ignore != null;
			}
			// next superclass
			acls = acls.getSuperclass();
		}

		/**
		 * check the public interface case. This must be manually searched for
		 * incase there is a public supersuperclass field hidden by a
		 * private/package superclass field. check up the superclass hierarchy
		 */
		Class<?>[] ints = clazz.getInterfaces();
		for (int i = 0; i < ints.length; i++) {

			// getField is fine here, because everything is public, and thus
			// it works
			try {
				return ints[i].getField(fieldName);
			} catch (NoSuchFieldException ignore) {
				assert ignore != null;
			}
		}

		if (match == null) {
			throw new NoSuchFieldException("The field[" + fieldName
					+ "] could not be found");
		}

		return match;
	}

	/**
	 * Gets all <code>Fields</code> in a class. Superclasses/interfaces will be
	 * considered.
	 * 
	 * @param clazz
	 *            the class to reflect, must not be null
	 * @return all fields in this class
	 */
	/**
	 * @name getAllFields
	 *
	 * @param clazz
	 * @return    
	 * Field[]     
	 * @throws 
	*/
	public static final Field[] getAllFields(final Class<?> clazz) {
		if (clazz == null) {
			throw new IllegalArgumentException("The class must not be null");
		}

		List<Field> fields = new ArrayList<Field>();

		Class<?> currentClazz = clazz;

		do {

			List<Field> fieldsInClass = Arrays.asList(currentClazz
					.getDeclaredFields());

			fields.addAll(fieldsInClass);
			currentClazz = currentClazz.getSuperclass();
		} while (currentClazz != Object.class);

		Class<?>[] interfaces = clazz.getInterfaces();
		for (Class<?> interfaze : interfaces) {

			List<Field> fieldsInInterface = Arrays
					.asList(interfaze.getFields());

			fields.addAll(fieldsInInterface);
		}

		return fields.toArray(EMPTY_FIELD_ARRAY);
	}

	/**
	 * @name getBeanFieldValueByName
	 *
	 * @param cls
	 * @param bean
	 * @param name
	 * @return    
	 * Object     
	 * @throws 
	*/
	public static Object getBeanFieldValueByName(Class<?> cls, Object bean,
			String name) {
		try {
			final Field f = cls.getDeclaredField(name);
			AccessController.doPrivileged(new PrivilegedAction<Void>() {
                public Void run() {
                	f.setAccessible(true);
                	return null;
                }
            });
			return f.get(bean);
		} catch (Exception e) {
			throw new IllegalArgumentException(e);
		}
	}

}